/*
 *   The class FourFundamentalOperation composed of operand and operator is for doing
 *   four fundamental operator
 *   
 *   The operand has the following type :
 *   	1)number with integer and float, ex 3, 0.4
 *   	2)variable with(without) reference checked from MyHashTree, ex object.price, $ref.cost
 *   The operand has the following type :
 *   	1)add : +
 *   	2)sub : -
 *   	3)multiply : *
 *   	4)divide : /
 *   	5)power : ^
 *   	6)left bracket : (
 *   	7)right bracket : )
 *   
 *       done by Lee in 2013/6/27
 */
package lee.base.xml;

import java.util.ArrayList;
import java.util.HashMap;
import lee.base.constant.ErrorMsg;
import lee.base.constant.FiniteStateMachine;
import lee.base.constant.Symbol;
import lee.base.constant.XmlAttribute;
import lee.base.structure.MyHashTree;
import lee.base.structure.Stack;

class StatementEle {
	
	public int type = -1;
	public String data = null;
	
	public StatementEle(int type, String data) {
		this.type = type;  //the type of data
		this.data = data;  //the content of data
	}
}

public class FourFundamentalOperation {
    
	//store a list of priority of operators
	protected HashMap<String, Integer> hashPriority = new HashMap<String, Integer>();
	//define symbol
	public static final char SYMBOL_VARIABLE = '$';
	/**
	 * Constructor : FourFundamentalOperation
	 */
	public FourFundamentalOperation() {
		//assign priority of operator
		this.hashPriority.put(Symbol.OP_POW, 3);
		this.hashPriority.put(Symbol.OP_MUL, 2);
		this.hashPriority.put(Symbol.OP_DIV, 2);
		this.hashPriority.put(Symbol.OP_ADD, 1);
		this.hashPriority.put(Symbol.OP_SUB, 1);
		this.hashPriority.put(Symbol.OP_LBK, 0);
		this.hashPriority.put(Symbol.OP_RBK, -1);
	}
	/**
	 * transfer the calculating statement to postfix
	 * @param statement - String
	 * @return a list of postfix - ArrayList<String>
	 */
	protected ArrayList<StatementEle> _001_transferPostfix(ArrayList<StatementEle> listStatement) {
		ArrayList<StatementEle> resultPosf = new ArrayList<StatementEle>();
		Stack<StatementEle> stack = new Stack<StatementEle>();
		int indexStatemant = 0;
		StatementEle getElement = null;
		boolean isEnd = false;
		while(indexStatemant < listStatement.size()) {
			getElement = listStatement.get(indexStatemant);
			if(getElement.type != Symbol.DATATYPE_OPERATOR) {  //it isn't an operator, write operand to postfix
				resultPosf.add(getElement);
			}
			else {  //it is an operator
				//first pop the operator from stack with it's 
				//priority is larger than current one, and then 
				//push current operator to stack
				isEnd = false;
				if(getElement.data.compareTo(Symbol.OP_LBK) == 0) {
					isEnd = true;
				}
				while(!isEnd) {
					StatementEle getEle = stack.GET001_element(stack.GET002_curTopIndex());
					if(getEle != null) {  //exist some element in stack
						//add all operand that the priority is larger than current one
						if(this._002_checkPriority(getElement.data) <= this._002_checkPriority(getEle.data)) {
							StatementEle popEle = stack.ACT002_pop();
							if(popEle.data.compareTo(Symbol.OP_LBK) != 0) {
							    resultPosf.add(popEle);
							}
							else {  //when encountering the "(", pushing continuously until meet ")"
								isEnd = true;
							}
						}
						else {  //finish pop from stack
							isEnd = true;
						}
					}
					else {  //stack empty
						isEnd = true;
					}
				}
				//push current operator to stack
				if(getElement.data.compareTo(Symbol.OP_RBK) != 0) {
				    stack.ACT001_push(getElement);
				}
			}
			indexStatemant = indexStatemant + 1;
		}
		//pop all the last element from stack and write them to postfix
		while((getElement = stack.ACT002_pop()) != null) {
			resultPosf.add(getElement);
		}
		return resultPosf;
	}
	/**
	 * get the priority value of symbol
	 * @param symbol - String
	 * @return the priority value - int
	 */
	protected int _002_checkPriority(String symbol) {
		return this.hashPriority.get(symbol);
	}
	/**
	 * check whether the symbol is an operator
	 * @param symbol - String
	 * @return true or false - boolean
	 */
	protected boolean _003_isOperator(String symbol) {
		if(this.hashPriority.get(symbol) == null) {
			return false;
		}
		return true;
	}
	/**
	 * check the type of passing data
	 * @param data - String
	 * @return the number type - int
	 */
	protected int _004_checkType(String data) {
		int type = Symbol.NONE;
		if(data.charAt(0) == XmlAttribute.SYMBOL_REF_VARIABLE) {
			type = Symbol.DATATYPE_REF_VARIABLE;
		}
		else if(this.ACT004_isVariableChar(data.charAt(0))) {
			type = Symbol.DATATYPE_VARIABLE;
		}
		else if(data.contains(".")) {
			type = Symbol.DATATYPE_NUM_FLOAT;
		}
		else {
			type = Symbol.DATATYPE_NUM_INTEGER;
		}
		return type;
	}
	/**
	 * calculate the statement - op1 operator op2
	 * @param op1 - String
	 * @param op2 - String
	 * @param operator - String
	 * @return the result of calculation - String
	 */
	protected String _005_calculate(String op1, String op2, String operator) {
		String result = null;
		double d_op1 = Double.parseDouble(op1);
		double d_op2 = Double.parseDouble(op2);
		if(operator.compareTo(Symbol.OP_ADD) == 0){
			result = Integer.toString((int)(d_op1 + d_op2));
		}
		else if(operator.compareTo(Symbol.OP_SUB) == 0) {
			result = Integer.toString((int)(d_op1 - d_op2));
		}
		else if(operator.compareTo(Symbol.OP_MUL) == 0) {
			result = Integer.toString((int)(d_op1 * d_op2));
		}
		else if(operator.compareTo(Symbol.OP_DIV) == 0) {
			result = Integer.toString((int)(d_op1 / d_op2));
		}
		else if(operator.compareTo(Symbol.OP_POW) == 0) {
			result = Integer.toString((int)Math.pow(d_op1, d_op2));
		}
		return result;
	}
	/**
	 * By using of predefining finite state machine, checking whether the 
	 * passing statement is legal, if legal, return the list of statement
	 * elements; otherwise return null
	 * @param statement - String
	 * @return ArrayList or null
	 */
	protected ArrayList<StatementEle> _006_checkStatement(String statement) {
		int state = FiniteStateMachine.STATE_START;
		int index = 0;
		int restBk = 0;
		String element = "";
		ArrayList<StatementEle> listStatement = new ArrayList<StatementEle>();
		while(state != FiniteStateMachine.STATE_FINISH && index < statement.length()) {
			char getChar = statement.charAt(index);
			if(state == FiniteStateMachine.STATE_START) {
				if(this.ACT002_isNumChar(getChar)) {  //a number
					state = FiniteStateMachine.STATE_NUM;
				}
				else if((getChar + "").compareTo(Symbol.OP_LBK) == 0) {  //a '('
					state = FiniteStateMachine.STATE_LBK;
				}
				else if(this.ACT004_isVariableChar(getChar)) { //a variable symbol
					state = FiniteStateMachine.STATE_VARIABLE;
				}
				else {  //error syntax
					state = FiniteStateMachine.STATE_FINISH;
				}
			}
			else if(state == FiniteStateMachine.STATE_VARIABLE) {
				if(this.ACT003_isCountOp(getChar)) {  //a counting operator
					state = FiniteStateMachine.STATE_OP;
				}
				else if((getChar + "").compareTo(Symbol.OP_RBK) == 0) {  //a ')'
					state = FiniteStateMachine.STATE_RBK;
				}
			}
			else if(state == FiniteStateMachine.STATE_NUM) {
				if(this.ACT002_isNumChar(getChar)) {  //a number
					state = FiniteStateMachine.STATE_NUM;
				}
				else if(this.ACT003_isCountOp(getChar)) {  //a counting operator
					state = FiniteStateMachine.STATE_OP;
				}
				else if(getChar == '.') {  //a float point
					state = FiniteStateMachine.STATE_POINT;
				}
				else if((getChar + "").compareTo(Symbol.OP_RBK) == 0) {  //a ')'
					state = FiniteStateMachine.STATE_RBK;
				}
				else {  //error syntax
					state = FiniteStateMachine.STATE_FINISH;
				}
			}
			else if(state == FiniteStateMachine.STATE_POINT) {
				if(this.ACT002_isNumChar(getChar)) {  //a number
					state = FiniteStateMachine.STATE_NUM;
				}
				else {  //error syntax
					state = FiniteStateMachine.STATE_FINISH;
				}
			}
			else if(state == FiniteStateMachine.STATE_OP) {
				if(this.ACT002_isNumChar(getChar)) {  //a number
					state = FiniteStateMachine.STATE_NUM;
				}
				else if((getChar + "").compareTo(Symbol.OP_LBK) == 0) {  //a '('
					state = FiniteStateMachine.STATE_LBK;
				}
				else if(this.ACT004_isVariableChar(getChar)) { //a variable symbol
					state = FiniteStateMachine.STATE_VARIABLE;
				}
				else {  //error syntax
					state = FiniteStateMachine.STATE_FINISH;
				}
			}
			else if(state == FiniteStateMachine.STATE_LBK) {
				restBk = restBk + 1;
				if(this.ACT002_isNumChar(getChar)) {  //a number
					state = FiniteStateMachine.STATE_NUM;
				}
				else if(this.ACT004_isVariableChar(getChar)) { //a variable symbol
					state = FiniteStateMachine.STATE_VARIABLE;
				}
				else {  //error syntax
					state = FiniteStateMachine.STATE_FINISH;
				}
			}
			else if(state == FiniteStateMachine.STATE_RBK) {
				restBk = restBk - 1;
				if(restBk < 0) {  //error syntax
					state = FiniteStateMachine.STATE_FINISH;
				}
				else if(this.ACT003_isCountOp(getChar)) {  //a counting operator
					state = FiniteStateMachine.STATE_OP;
				}
				else {  //error syntax
					state = FiniteStateMachine.STATE_FINISH;
				}
			}
			//set element
			if(state == FiniteStateMachine.STATE_NUM ||   //set as number or variable
			   state == FiniteStateMachine.STATE_POINT ||
			   state == FiniteStateMachine.STATE_VARIABLE) {
				element = element + getChar;
			}
			else {  //set as operator
				if(element.compareTo("") != 0) {  //store the operand
					listStatement.add(new StatementEle(this._004_checkType(element), element));
					element = "";
				}
				listStatement.add(new StatementEle(Symbol.DATATYPE_OPERATOR, getChar + ""));  //store the operator
			}
			index = index + 1;
		}
		//check final "("
		if(state == FiniteStateMachine.STATE_RBK) {
			restBk = restBk - 1;
		}
		//add final number
		if(element.compareTo("") != 0) {  //store the operand
			listStatement.add(new StatementEle(this._004_checkType(element), element));
			element = "";
		}
		//check whether legal statement
		if((restBk == 0) && 
		   (state == FiniteStateMachine.STATE_RBK || 
		    state == FiniteStateMachine.STATE_NUM || 
		    state == FiniteStateMachine.STATE_VARIABLE)) {
			return listStatement;
		}
		return null;
	}
	/**
	 * given a statement, fist doing the postfix transformation, and then
	 * calculate the result
	 * @param statement - String
	 * @param if operand is variable, checking the lookUpTable - MyHashTree
	 * @return the result.
	 *         If the syntax of statement is error, return MSG_STATEMENT_SYNTAX_ERROR - String
	 */
	public String ACT001_operate(String statement, MyHashTree lookUpTable) {
		String countResult = null;
		//check and get statement list
		ArrayList<StatementEle> listStatement = this._006_checkStatement(statement);
		if(listStatement != null) {
			//change statement list into posfix list
			ArrayList<StatementEle> postfix = this._001_transferPostfix(listStatement);
			//start to calculate result
			Stack<StatementEle> stack = new Stack<StatementEle>();
			int index = 0;
			while(postfix != null && index < postfix.size()) {
				StatementEle getEle = postfix.get(index);
				if(getEle.type != Symbol.DATATYPE_OPERATOR) {  //get the operand, push to stack
					if(getEle.type == Symbol.DATATYPE_VARIABLE || 
					   getEle.type == Symbol.DATATYPE_REF_VARIABLE) {
						getEle.data = lookUpTable.ACT002_getSpecificValue(getEle.data, null);
						if(getEle.data != null && getEle.data.contains(XmlAttribute.VARIABLE_POINT + "")) {  //data contains variable
							postfix = null;
							countResult = ErrorMsg.MSG_GET_CONTENT_VARIABLE;
						}
					}
					//check whether the variable is legal
					if(getEle.data == null) {
						postfix = null;
					}
					else {
					    stack.ACT001_push(getEle);
					}
				}
				else {  //get the operator, pop and do calculation
					String op1 = stack.ACT002_pop().data;
					String op2 = stack.ACT002_pop().data;
					stack.ACT001_push(new StatementEle(0, this._005_calculate(op2, op1, getEle.data)));
				}
				index = index + 1;
			}
			//get final result
			if(postfix != null) {
			    countResult = stack.ACT002_pop().data;
			}
			else if(countResult == null) {
				countResult = ErrorMsg.MSG_XML_VARIABLE_SYNTAX_ERROR;
			}
		}
		else {
			countResult = ErrorMsg.MSG_STATEMENT_SYNTAX_ERROR;
		}
		return countResult;
	}
	/**
	 * check whether the passing character is a number
	 * @param num - char
	 * @return true/false
	 */
	public boolean ACT002_isNumChar(char num) {
		return (num >= '0' && num <= '9');
	}
	/**
	 * check whether the passing character is an operation
	 * @param op - char
	 * @return true/false
	 */
	public boolean ACT003_isCountOp(char op) {
		String opStr = op + "";
		return (opStr.compareTo(Symbol.OP_ADD) == 0) ||
			   (opStr.compareTo(Symbol.OP_SUB) == 0) ||
			   (opStr.compareTo(Symbol.OP_MUL) == 0) ||
			   (opStr.compareTo(Symbol.OP_DIV) == 0) ||
			   (opStr.compareTo(Symbol.OP_POW) == 0);
	}
	/**
	 * check whether the passing character is illegal variable composing
	 * @param va - char
	 * @return true/false
	 */
	public boolean ACT004_isVariableChar(char va) {
		return (va >= 'a' && va <= 'z') ||
			   (va >= 'A' && va <= 'Z') ||
			   (va == XmlAttribute.SYMBOL_REF_VARIABLE);
	}
}
